package com.xhj.mybatis.scripting.xmltags;

import com.xhj.mybatis.session.Configuration;

import java.util.*;

/**
 * TrimSqlNode
 *
 * @author XJks
 * @description 用于处理 SQL 语句的前缀和后缀修剪（如去除多余的 AND/OR），并在需要时添加指定的前缀或后缀。
 */
public class TrimSqlNode implements SqlNode {

    /**
     * SQL 节点内容，通常是其他 SqlNode 的组合
     */
    private final SqlNode contents;

    /**
     * 需要添加到 SQL 前面的前缀（如 WHERE、AND 等）
     */
    private final String prefix;

    /**
     * 需要添加到 SQL 后面的后缀（如 ）等）
     */
    private final String suffix;

    /**
     * 需要去除的前缀列表（如 AND、OR 等）
     */
    private final List<String> prefixesToOverride;

    /**
     * 需要去除的后缀列表
     */
    private final List<String> suffixesToOverride;

    /**
     * MyBatis 配置对象
     */
    private final Configuration configuration;

    /**
     * 构造方法，接收字符串形式的前缀/后缀去除列表，并解析为 List
     *
     * @param configuration      MyBatis 的配置对象
     * @param contents           SQL 节点内容（通常是其他 SqlNode 的组合）
     * @param prefix             需要添加到 SQL 前面的前缀（如 WHERE、AND 等）
     * @param prefixesToOverride 需要去除的前缀列表（字符串形式，如 "AND|OR"）
     * @param suffix             需要添加到 SQL 后面的后缀（如 ")" 等）
     * @param suffixesToOverride 需要去除的后缀列表（字符串形式，如 "AND|OR"）
     */
    public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
        // 调用另一个构造方法，将字符串形式的前/后缀去除列表解析为 List 形式
        this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
    }

    /**
     * 构造方法，直接接收 List 形式的前缀/后缀去除列表
     *
     * @param configuration      MyBatis 的配置对象
     * @param contents           SQL 节点内容
     * @param prefix             需要添加到 SQL 前面的前缀
     * @param prefixesToOverride 需要去除的前缀列表（List 形式）
     * @param suffix             需要添加到 SQL 后面的后缀
     * @param suffixesToOverride 需要去除的后缀列表（List 形式）
     */
    protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
        // 保存 SQL 节点内容
        this.contents = contents;
        // 保存需要添加的前缀
        this.prefix = prefix;
        // 保存需要去除的前缀列表
        this.prefixesToOverride = prefixesToOverride;
        // 保存需要添加的后缀
        this.suffix = suffix;
        // 保存需要去除的后缀列表
        this.suffixesToOverride = suffixesToOverride;
        // 保存 MyBatis 配置对象
        this.configuration = configuration;
    }

    /**
     * 应用 SQL 节点，将内容写入 context，并处理前后缀
     *
     * @param context 包含正在构建的 SQL 文本、参数映射、变量等运行时信息。
     * @return 如果内容节点成功应用，则返回 true；否则返回 false。
     */
    @Override
    public boolean apply(DynamicContext context) {
        // 包装原始 context，创建一个用于处理前后缀的 FilteredDynamicContext
        FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
        // TODO:注意这里需要先使用子结点组合 contents 处理 context，子结点们 SqlNode 会把 SQL 片段写到这个 filteredDynamicContext 子类的 sqlBuffer 中，等所有子结点处理完后，在调用 filteredDynamicContext.applyAll() 方法统一处理前后缀，然后把最终结果追加到父类的 sqlBuilder 中。
        boolean result = contents.apply(filteredDynamicContext);
        // TODO:再使用本类的前后缀处理逻辑，处理收集到的 SQL
        filteredDynamicContext.applyAll();
        return result;
    }

    /**
     * 将字符串形式的去除列表（如 "AND|OR"）解析为 List
     *
     * @param overrides 需要去除的前/后缀字符串，多个用 | 分隔
     * @return 解析后的字符串列表（全部大写），用于后续比较和处理
     */
    private static List<String> parseOverrides(String overrides) {
        // 如果传入的字符串不为 null
        if (overrides != null) {
            // 用 | 作为分隔符，把字符串拆分成多个部分
            final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
            // 创建一个列表，容量等于分割出来的部分数量
            final List<String> list = new ArrayList<>(parser.countTokens());
            // 遍历所有分割出来的部分
            while (parser.hasMoreTokens()) {
                // 取出下一个部分，并转为大写（方便后续比较时不区分大小写）
                list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
            }
            // 返回处理好的列表
            return list;
        }
        // 如果传入的是 null，返回一个空列表
        return Collections.emptyList();
    }

    /**
     * 内部类，继承 DynamicContext，用于收集 SQL 并处理前后缀
     */
    private class FilteredDynamicContext extends DynamicContext {
        /**
         * 委托的原始上下文
         */
        private DynamicContext delegate;

        /**
         * 前缀是否已处理
         */
        private boolean prefixApplied;

        /**
         * 后缀是否已处理
         */
        private boolean suffixApplied;

        /**
         * 用于收集 SQL 的缓冲区
         * <p>
         * TODO:
         *  疑问:父类也有一个 sqlBuilder，这里为什么还要重新定义一个？
         *  回答:
         *      1.父类的 sqlBuilder 是用来收集最终的 SQL 语句的，是全局的。
         *      2.而这个子类中的 sqlBuffer 是用来临时收集 SQL 片段的，在处理前后缀之前使用，是局部的。
         *      3.通过这种方式，可以先在 sqlBuffer 中处理前后缀，处理完后再把结果写回到父类的 sqlBuilder 中。
         *      4.子结点组合  SqlNode 会把 SQL 片段写到这个 sqlBuffer 中，等所有子结点处理完后，再统一处理前后缀，然后把最终结果追加到父类的 sqlBuilder 中。
         */
        private StringBuilder sqlBuffer;

        /**
         * 构造方法，初始化缓冲区和标志位
         */
        public FilteredDynamicContext(DynamicContext delegate) {
            super(configuration, null);
            this.delegate = delegate;
            this.prefixApplied = false;
            this.suffixApplied = false;
            this.sqlBuffer = new StringBuilder();
        }

        /**
         * 处理 SQL 的前后缀，并写回原始上下文
         */
        public void applyAll() {
            // 去除首尾空格
            sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
            // 转为大写用于比较
            String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
            // 如果 SQL 不为空
            if (trimmedUppercaseSql.length() > 0) {
                // 处理前缀
                applyPrefix(sqlBuffer, trimmedUppercaseSql);
                // 处理后缀
                applySuffix(sqlBuffer, trimmedUppercaseSql);
            }
            // TODO:使用 delegate 将处理好的 SQL 写回原始上下文，delegate 是最终的 DynamicContext
            delegate.appendSql(sqlBuffer.toString());
        }

        /**
         * 暴露出 bindings，返回一个 Map<String, Object> 视图，外部可以读取已绑定的变量
         *
         * @return 绑定变量的 Map 视图
         */
        @Override
        public Map<String, Object> getBindings() {
            return delegate.getBindings();
        }

        /**
         * 在上下文中绑定一个名字和值
         *
         * @param name  绑定变量的名称
         * @param value 绑定变量的值
         */
        @Override
        public void bind(String name, Object value) {
            delegate.bind(name, value);
        }

        /**
         * 返回一个唯一数字（先返回当前值，然后自增），常用于生成占位符或变量名
         *
         * @return 唯一数字
         */
        @Override
        public int getUniqueNumber() {
            return delegate.getUniqueNumber();
        }

        /**
         * 将一段 SQL 片段追加到 sqlBuilder 中（片段之间用空格隔开）
         *
         * @param sql 要追加的 SQL 片段
         */
        @Override
        public void appendSql(String sql) {
            sqlBuffer.append(sql);
        }

        /**
         * 返回当前拼接好的 SQL 字符串，去除首尾多余空白
         *
         * @return 拼接好的 SQL 字符串
         */
        @Override
        public String getSql() {
            return delegate.getSql();
        }

        /**
         * 处理 SQL 前缀：去除指定前缀并添加新前缀
         *
         * @param sql                 用于收集 SQL 的缓冲区
         * @param trimmedUppercaseSql 去除首尾空格并转为大写的 SQL 字符串
         */
        private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
            // 如果前缀还没有被处理过
            if (!prefixApplied) {
                // 标记前缀已经处理过，避免重复处理
                prefixApplied = true;
                // 如果有需要去除的前缀列表
                if (prefixesToOverride != null) {
                    // 遍历所有需要去除的前缀
                    for (String toRemove : prefixesToOverride) {
                        // 如果当前 SQL（已去首尾空格并转大写）是以某个前缀开头
                        if (trimmedUppercaseSql.startsWith(toRemove)) {
                            // 从 SQL 的开头删除这个前缀（去除多余的 AND/OR 等）
                            sql.delete(0, toRemove.trim().length());
                            // 只去除第一个匹配的前缀，处理完就跳出循环
                            break;
                        }
                    }
                }
                // 如果有需要添加的新前缀（如 WHERE、AND 等）
                if (prefix != null) {
                    // 在 SQL 最前面插入一个空格，保证格式美观
                    sql.insert(0, " ");
                    // 在 SQL 最前面插入指定的新前缀
                    sql.insert(0, prefix);
                }
            }
        }


        /**
         * 处理 SQL 后缀：去除指定后缀并添加新后缀
         *
         * @param sql                 用于收集 SQL 的缓冲区
         * @param trimmedUppercaseSql 去除首尾空格并转为大写的 SQL 字符串
         */
        private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
            // 如果后缀还没有被处理过
            if (!suffixApplied) {
                // 标记后缀已经处理过，避免重复处理
                suffixApplied = true;
                // 如果有需要去除的后缀列表
                if (suffixesToOverride != null) {
                    // 遍历所有需要去除的后缀
                    for (String toRemove : suffixesToOverride) {
                        // 如果当前 SQL（已去首尾空格并转大写）是以某个后缀结尾
                        if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
                            // 计算要删除的后缀的起始和结束位置
                            int start = sql.length() - toRemove.trim().length();
                            int end = sql.length();
                            // 从 SQL 的结尾删除这个后缀（去除多余的 AND/OR 等）
                            sql.delete(start, end);
                            // 只去除第一个匹配的后缀，处理完就跳出循环
                            break;
                        }
                    }
                }
                // 如果有需要添加的新后缀（如 ")" 等）
                if (suffix != null) {
                    // 在 SQL 结尾添加一个空格，保证格式美观
                    sql.append(" ");
                    // 在 SQL 结尾添加指定的新后缀
                    sql.append(suffix);
                }
            }
        }

    }

}
